---
name: html-to-pptx
description: Convert a single-file HTML slide deck into a pixel-perfect PPTX by rendering it in headless Chrome, taking a full-page screenshot, slicing it into per-slide images with Pillow, and packing them into a PowerPoint file. Use when the user has an HTML presentation and wants a .pptx export.
---
# HTML to PPTX Skill
Convert any single-file HTML slide deck into a `.pptx` file that looks exactly like the browser render — fonts, glows, gradients, animations frozen at their initial state.
## How It Works
1. **Headless Chrome** (via Puppeteer) renders the HTML at 1920×1080 and takes a single `fullPage: true` screenshot → one tall PNG
2. **Pillow** slices that PNG into N equal strips (one per slide)
3. **python-pptx** packs each strip as a full-bleed image on its own PPTX slide
This avoids all scroll/clip coordinate bugs — each slide is a guaranteed unique pixel crop.
---
## Requirements Check
Before running, verify these are available:
- `node` + `puppeteer` npm package (installed locally or globally)
- `python` with `pillow` and `python-pptx` packages
- Chrome or Edge executable on the system
---
## Step-by-Step Instructions
### 1. Ask the user for inputs
You need:
- **HTML file path** — the source presentation
- **Number of slides** — how many slides the deck has
- **Output PPTX filename** — where to save (default: same folder as HTML, same name with `.pptx`)
- **Slide aspect ratio** — default 16:9 (1920×1080). Ask only if non-standard.
- **Chrome path** — auto-detect from common locations, only ask if not found
Auto-detect Chrome from these paths (check in order):
- `C:/Program Files/Google/Chrome/Application/chrome.exe`
- `C:/Program Files (x86)/Google/Chrome/Application/chrome.exe`
- `C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe`
- `/usr/bin/google-chrome`
- `/usr/bin/chromium-browser`
### 2. Install puppeteer if needed
```bash
# Check
node -e "require('puppeteer')" 2>&1
# Install locally in the HTML file's directory if missing
npm install puppeteer --save-dev
```
### 3. Write the screenshot script
Write `_screenshot.mjs` to the same folder as the HTML:
```js
import puppeteer from 'puppeteer';
import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const HTML_PATH = path.join(__dirname, '');
const W = 1920;
const H = 1080;
const browser = await puppeteer.launch({
executablePath: '',
headless: true,
args: ['--no-sandbox', `--window-size=${W},${H}`],
});
const page = await browser.newPage();
await page.setViewport({ width: W, height: H, deviceScaleFactor: 1 });
await page.goto(`file:///${HTML_PATH.replace(/\\/g, '/')}`, {
waitUntil: 'networkidle0',
timeout: 30000,
});
// Wait for fonts and transitions to settle
await new Promise(r => setTimeout(r, 2500));
// Freeze animations, hide fixed UI chrome
await page.addStyleTag({ content: `
* { animation: none !important; transition: none !important; }
body::after { display: none !important; }
#nav, #progress, nav, header[data-fixed] { display: none !important; }
` });
await page.screenshot({
path: path.join(__dirname, '_fullpage.png'),
fullPage: true,
});
console.log('Done.');
await browser.close();
```
### 4. Write the slice + pack script
Write `_pack_pptx.py` to the same folder:
```python
from PIL import Image
from pptx import Presentation
from pptx.util import Inches
import os
FOLDER = r''
FULLPAGE = os.path.join(FOLDER, '_fullpage.png')
OUT = os.path.join(FOLDER, '')
SLIDES =
img = Image.open(FULLPAGE)
W, H = img.size
slide_h = H // SLIDES
print(f"Full image: {W}x{H}, {SLIDES} slides, {slide_h}px each")
prs = Presentation()
prs.slide_width = Inches(20) # 1920px at 96dpi
prs.slide_height = Inches(11.25) # 1080px at 96dpi
blank = prs.slide_layouts[6]
for i in range(SLIDES):
top = i * slide_h
crop = img.crop((0, top, W, top + slide_h))
tmp = os.path.join(FOLDER, f'_slide_{i+1:02d}.png')
crop.save(tmp)
slide = prs.slides.add_slide(blank)
slide.shapes.add_picture(tmp, left=0, top=0,
width=prs.slide_width,
height=prs.slide_height)
print(f" Slide {i+1:02d} rows {top}-{top+slide_h}")
prs.save(OUT)
print(f"Saved: {OUT}")
```
### 5. Run both scripts
```bash
node _screenshot.mjs
python _pack_pptx.py
```
### 6. Clean up temp files
After confirming the PPTX looks correct, delete:
- `_fullpage.png`
- `_screenshot.mjs`
- `_pack_pptx.py`
- `_slide_01.png` … `_slide_NN.png`
---
## Common Issues
| Problem | Cause | Fix |
|---|---|---|
| All slides identical | Using `clip:{y:0}` on scrolled viewport | Always use `fullPage:true` + slice — never scroll |
| PermissionError on save | PPTX open in PowerPoint | Close the file first, or save to a new filename |
| Fonts wrong in PPTX | Custom web fonts not installed locally | This approach embeds pixel images so fonts are always correct |
| Slide count wrong | `H // SLIDES` rounding | Check `H % SLIDES == 0`; if not, slides have `min-height` inconsistency in HTML |
| Chrome not found | Wrong exe path | Check Edge as fallback |